Technical Note TN2060
ICM Drawing non-scheduled frames with QuickTime 6

目次

このテクニカルノートは、Mac OS X の QuickTime 6 において Image Compression Manager が不定期フレームを描画する方法に加えられた変更点について説明します。

[2002 年 8 月 21 日]






はじめに

Mac OS X のウィンドウでは、ダブルバッファ処理が行われています。つまり、すべてのウィンドウには、オフスクリーンバッファが関連付けられており、アプリケーションは、QuickDraw、Quartz、QuickTime、または OpenGL を使ってオフスクリーンバッファに描画します。アプリケーションがオフスクリーンバッファへの描画を終えると、画像は Quartz Compositor によりフレームバッファにコピーされます。画像を Quartz Compositor に送ることをフラッシュといいます。

通常は、Mac OS X がウィンドウのフラッシュを行うので、アプリケーションでは考慮する必要がありません。しかし、QuickTime 6 ではウィンドウフラッシュの処理方法が変更されたので、アプリケーションの描画動作が変わるかもしれません。

先頭に戻る

用語解説

このテクニカルノートでは、次のように用語を定義します。

  • フラッシュ とは、ウィンドウのオフスクリーンバッファの一部を、スクリーンに表示させるために Quartz Compositor に送ることをいいます。

  • 実行ループとは、イベントなどのタスクへの入力ソースを監視し、それらを処理するためにコールバック関数を呼び出し、それ以外のときはスリープする Core Foundation サービスの 1 つです。Carbon と Cocoa のイベントループは、実行ループを利用して実装されます。

  • 伸長は、Image Compression Manager が仲介する描画操作です。ビデオのフレーム、Graphics Importer コンポーネントを使って描画された画像、および圧縮画像などがあります。

  • 定期伸長は、指定の時間に実行されるように設定された伸長です。

  • 不定期伸長は、すぐに実行される伸長です。

Graphics Importer と圧縮画像は、必ず不定期伸長を使います。QuickTime は、動画を再生する時には、すべてのビデオフレームを描画するために定期伸長を使おうとします。その方が、フレーム表示のタイミングがより正確になるからです。しかし、QuickTime が不定期伸長に頼らざるを得なくなる場合があります。ビデオコーデックがスケジューリングをサポートしていない場合、およびムービーの再生に利用できる CPU の能力が十分でない場合などです。

先頭に戻る

アプリケーションが QuickDraw を使って描画するときに何が起こるのか

QuickDraw は、各 GrafPort に関連付けられている「ダーティリージョン」を維持しています。アプリケーションが、GrafPort に描画すると、描画した領域は、ポートのダーティリージョンに追加されます。アプリケーションが実行ループに戻ると、Carbon がウィンドウリストを一巡りし、QDFlushPortBuffer を呼び出すことによってダーティリージョンをフラッシュします。つまり、描画操作に少し時間を要したとしても、すべてが、一度のリフレッシュでスクリーン上に表示されます。

先頭に戻る

QuickTime 5 はどのように行っていたのか

QuickTime 5 では、定期伸長と不定期伸長のどちらに対しても同じ動作を行っていました。QuickTime 5 は、描画し終わるとすぐにその画像をスクリーンにフラッシュしていました。

これは、ムービーを再生する場合には正しい方法です。すべてのビデオフレームは即座に表示される必要があるからです。しかし、たとえば、Graphics Importer により描画された静止画とその他の QuickDraw による描画を一緒に扱うアプリケーションの場合は、見栄えが悪くなります。なぜなら、静止画像は、他のどの画像よりも先にスクリーン上に表示されてしまうからです。つまり、一度できれいにリフレッシュされません。

先頭に戻る

QuickTime 6 では何が変わったのか?

QuickTime 6 は、定期伸長後に画像をスクリーンにフラッシュします。

QuickTime 6 では、不定期伸長後にフラッシュは行いません。その代わりに、GrafPort に関連付けられている QuickDraw のダーティリージョンに描画領域を追加し、実行ループがその他の描画内容と共にダーティリージョンをフラッシュするのを待ちます。 したがって、Graphics Importer と QuickDraw を一緒に使うアプリケーションでも、以前よりもスクリーンの更新はきれいになります。

先頭に戻る

それでは問題は何か

Image Compression Manager の変更により、不定期な描画も QuickDraw の描画と同時に表示されますが、アプリケーションが次に該当する場合は、描画操作の結果が実際に表示されないかもしれません。

  1. Carbon ウィンドウではないオンスクリーンポートを使用している場合、または

  2. 実行ループを定期的には実行させていない場合(WaitNextEvent への呼び出しを行わずに短いループで待機している、または Carbon イベントハンドラーから制御を戻さないなど)

Carbon ウィンドウでないオンスクリーンポートの一例は、アプリケーションのドックタイルのポートです。 興味のある人のために述べると、BeginQDContextForApplicationDockTile を呼び出すことによって作成できます。

先頭に戻る

アプリケーションがこの問題に遭遇した場合どうすれば良いか

この描画の問題を回避するには、アプリケーションに次の中から 1 つ以上を実装することをお勧めします。

  1. 描画を Carbon ウィンドウで行う場合には、実行ループを実行させます。

  2. 不定期伸長の後で、QDFlushPortBuffer を呼び出し、明示的にフラッシュさせます。

  3. 伸長シーケンスとして、Image Compression Manager シーケンスのcodecDSequenceFlushInsteadOfDirtying フラグをセットし、Image Compression Manager が暗黙的にフラッシュするようにします。これは、SetDSequenceFlags API を使って行えます。

  4. ムービーの、hintsFlushVideoInsteadOfDirtyingムービー再生ヒントを設定します。これは、 SetMoviePlayHints API を使って行えます。

これらの各方法を詳しく見ていきましょう。

1. 実行ループを実行させる

実行ループを実行させないと、様々なことが行われなくなります。 Carbon のイベントループタイマが作動しなくなります。アプリケーションはイベントを受け付けなくなります(そのためドックは、アプリケーションが反応していないと報告します)。またウィンドウのダーティリージョンがフラッシュされなくなります。可能ならば、イベントハンドラが戻るようにコードを再構成することを検討します。場合によっては、Carbon イベントループタイマを使用すれば、コードが簡素化される上、CPU 消費量を減らせます。

アプリケーションが Carbon ウィンドウではないオンスクリーンポートに描画している場合、この方法は効果がありません。なぜなら、Carbon ウィンドウではない場合は、Carbon がウィンドウリストを一巡りしても、描画内容が見つからないからです。

2. QDFlushPortBufferを呼び出す

QuickTime を使って短いアニメーションを描くときに、描画が完了するまでイベントハンドラが戻るようにしないと都合が悪い場合には、この方法がふさわしいでしょう。

void QDFlushPortBuffer( CGrafPtr port, RgnHandle region );
  • port - CGrafPortレコードへのポインタ



  • region - フラッシュの対象となる明示的なリージョンへのハンドル。 現在のダーティリージョンをフラッシュするには、NULLを渡します。

 

 リスト 1 Graphics Importerを使って画像を描画し回転させる

WindowRef gWindow;

OSErr DoSillyRotation( const FSSpecPtr inFSSpec )
{
    GraphicsImportComponent importer = 0;
    Rect                    naturalBounds,
                            windowBounds;
    MatrixRecord            matrix;
    UInt32                  rotation = 0;
    OSErr                   err = noErr;

    err = GetGraphicsImporterForFile( inFSSpec, &importer );
    if ( err ) goto bail;

    // 画像の本来のサイズを取得する
    err = GraphicsImportGetNaturalBounds( importer, &naturalBounds );
    if ( err ) goto bail;

    windowBounds = naturalBounds;
    OffsetRect( &windowBounds, 10, 45 );
    gWindow = NewCWindow( NULL, &windowBounds,
                         "¥pSilly GImport Rotate", true, documentProc,
                         (WindowPtr)-1, true, 0);
    if ( NULL == gWindow ) goto bail;

    // 描画用にグラフィックスポートを設定
    err = GraphicsImportSetGWorld( importer,
                       GetWindowPort( gWindow ), NULL );
    if ( err ) goto bail;

    // 回転を試行 
    do {

        // 行列をリセット
        SetIdentityMatrix( &matrix );

        // rotation に指定された度数だけ右回転する回転操作を定義するように
        // 行列の内容を変更
        RotateMatrix( &matrix,
              Long2Fix( rotation ),
              Long2Fix(( naturalBounds.right - naturalBounds.left ) / 2 ),
              Long2Fix(( naturalBounds.bottom - naturalBounds.top ) / 2 ));

        // 変換行列を設定
        GraphicsImportSetMatrix( importer, &matrix );

        // 描画する
        err = GraphicsImportDraw( importer );
        if ( err ) break;

        // 明示的にフラッシュ - QuickTime 6 では必須
        QDFlushPortBuffer( GetWindowPort( gWindow ), NULL );

        rotation += 30;

    } while ( rotation <= 360 );

bail:
    if ( importer ) CloseComponent( importer );

    return err;
}             

3. Image Compression Manager にフラッシュさせる

Image Compression Manager の伸長シーケンスを直接使用する場合は、QuickTime 5 の場合と同様、フラグをセットして Image Compression Manager にフラッシュさせます。

enum {
    codecDSequenceFlushInsteadOfDirtying = (1L << 8)
};

 リスト 2 伸長シーケンスを使って画像を描画し回転させる

WindowRef gWindow;

OSErr DoDSeqSillyRotation( const FSSpecPtr inFSSpec )
{
    GraphicsImportComponent importer = 0;
    Rect                    naturalBounds,
                            windowBounds;
    ImageSequence           seqID = 0;
    ImageDescriptionHandle  desc = NULL;
    Ptr                     pData = NULL;
    MatrixRecord            matrix;
    UInt32                  rotation = 0;
    GrafPtr                 savedPort;
    OSErr                   err = noErr;

    GetPort(&savedPort);

    err = GetGraphicsImporterForFile( inFSSpec, &importer );
    if ( err ) goto bail;

    // このインポータに関連付けられている画像の本来のサイズを取得する
    err = GraphicsImportGetNaturalBounds( importer, &naturalBounds );
    if ( err ) goto bail;

    windowBounds = naturalBounds;
    OffsetRect( &windowBounds, 10, 45 );
    gWindow = NewCWindow( NULL, &windowBounds,
                         "¥pSilly DSequence Rotate", true, documentProc,
                         (WindowPtr)-1, true, 0);
    if ( NULL == gWindow ) goto bail;

    SetPortWindowPort( gWindow );

    // 画像に関する記述を取得
    err = GraphicsImportGetImageDescription( importer, &desc );
    if ( err ) goto bail;

    // 画像データをどこかに貼り付ける必要がある
    pData = NewPtrClear( (**desc).dataSize );
    if ( MemError() || NULL == pData ) goto bail;

    // 画像データを取得
    err = GraphicsImportReadData( importer, pData, 0, (**desc).dataSize );
    if ( err ) goto bail;

    // *** 伸長シーケンスを使って描画する  ***

    // シーケンスの開始
    err = DecompressSequenceBeginS( &seqID, desc, NULL, 0,
                                    GetWindowPort( gWindow ), NULL, NULL,
                                    NULL, srcCopy, NULL, 0, codecNormalQuality,
                                    anyCodec );
    if ( err ) goto bail;

    // QuickTime 6 では、 伸長シーケンスとして 
    // codecDSequenceFlushInsteadOfDirtying フラグをセットすると、
    // Image Compression Manager によって暗黙的にフラッシュが行われる。
    // QuickTime 5 でこのフラグをセットしても何の効果もない
    SetDSequenceFlags( seqID,
                       codecDSequenceFlushInsteadOfDirtying,
                       codecDSequenceFlushInsteadOfDirtying );

    // 回転を実行
    do {

        // 行列をリセット
        SetIdentityMatrix( &matrix );

       // rotation に指定された度数だけ右回転する回転操作を定義するように
        // 行列の内容を変更
        RotateMatrix( &matrix,
                      Long2Fix( rotation ),
                      Long2Fix(( naturalBounds.right - naturalBounds.left ) / 2 ),
                      Long2Fix(( naturalBounds.bottom - naturalBounds.top ) / 2 ));

        // 描画に使用する変換行列を設定
        SetDSequenceMatrix( seqID, &matrix );

        // 描画する
        err = DecompressSequenceFrameS( seqID, pData, (**desc).dataSize, 0,
                                        NULL, NULL );
        if ( err ) goto bail;

        rotation += 30;

    } while ( rotation <= 360 );

bail:
    if ( seqID ) CDSequenceEnd( seqID );
    if ( importer ) CloseComponent( importer );
    if ( desc ) DisposeHandle( (Handle)desc );
    if ( pData ) DisposePtr( pData );

    SetPort( savedPort );

    return err;
}            

注意:
QuickTime 5で、codecDSequenceFlushInsteadOfDirtying フラグを設定しても何の効果もありません。


4. Movie Toolbox にフラッシュさせる

ムービーを再生する場合は、再生ヒントを設定すれば、ムービーをフラッシュさせることができます。 定期伸長を使ってまたはハードウェアアクセラレーションを使って再生するムービーの場合には、これは必要ないでしょう。アニメーション GIF は、このどちらも使わないので、ムービーをアニメーション GIF に変換した後、フレームが表示されない場合には、この再生ヒントを設定する必要があるでしょう。

enum {
    hintsFlushVideoInsteadOfDirtying = (1L << 22)
};

 リスト 3 ムービーとしてアニメーション GIF ファイルを再生する

WindowRef gWindow;
Movie     gMovie;

OSErr PlayAnimatedGIF( const FSSpecPtr inGifFile )
{
    short     refNum = 0;
    Rect      bounds;
    long      numberOfSamples;
    GrafPtr   savedPort;
    OSErr     err;

    GetPort( &savedPort );

    err = OpenMovieFile( inGifFile, &refNum, fsRdPerm );
    if (err) goto bail;

    // GIF からムービーを作成
    err = NewMovieFromFile( &gMovie, refNum, NULL, NULL, newMovieActive, NULL );
    CloseMovieFile( refNum );
    if ( err || NULL == gMovie ) goto bail;

    GetMovieNaturalBoundsRect( gMovie, &bounds );
    OffsetRect( &bounds, -bounds.left, -bounds.top );
    OffsetRect( &bounds, 10, 45 );
    gWindow = NewCWindow( NULL, &bounds,
                         "¥pPlay Animated GIF", true, documentProc,
                         (WindowPtr)-1, true, 0 );

    SetPortWindowPort( gWindow );

    // ムービーの GWorld を設定
    SetMovieGWorld( gMovie, GetWindowPort( gWindow ), NULL );

    // サンプルカウントを取得。これにより画像がアニメーションされているかどうかがわかる
    numberOfSamples =
          GetMediaSampleCount( GetTrackMedia( GetMovieIndTrack( gMovie, 1 ) ));

    // アニメーションされている場合 ― QuickTime 6 では、
    // hintsFlushVideoInsteadOfDirtying 再生ヒントを設定する
    if ( numberOfSamples > 1 ) {
        SetMoviePlayHints( gMovie,
                           hintsFlushVideoInsteadOfDirtying,
                           hintsFlushVideoInsteadOfDirtying );
   }

    StartMovie( gMovie );

    do {
        MoviesTask( gMovie, 0 );
    } while ( !IsMovieDone( gMovie ) );

bail:
    SetPort( savedPort );

    return err;
}

再生ヒントは、メディアハンドラに渡されます。メディアハンドラは、再生ヒントに従って、QDFlushPortBuffer を呼び出すか、使用するすべての伸長シークエンスに上述の codecDSequenceFlushInsteadOfDirtying を設定して、確実に描画内容をフラッシュさせる必要があります。


注意:
QuickTime 5 で、hintsFlushVideoInsteadOfDirtying 再生ヒントを設定しても何の効果もありません。


先頭に戻る

参考文献

QDFlushPortBuffer

SetDSequenceFlags

SetMoviePlayHints

Working with Sequences

先頭に戻る

ダウンロード

Acrobat gif

Acrobat version of this Note (44K)

ダウンロード


先頭に戻る